package com.atguigu.service.impl;

import com.atguigu.entity.BaseBrand;
import com.atguigu.exception.SleepUtils;
import com.atguigu.mapper.BaseBrandMapper;
import com.atguigu.service.BaseBrandService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>
 * 品牌表 服务实现类
 * </p>
 *
 * @author GuoJH
 * @since 2021-08-24
 */
@Service
public class BaseBrandServiceImpl extends ServiceImpl<BaseBrandMapper, BaseBrand> implements BaseBrandService {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    //测试redis缓存
    //@Override
    public synchronized void setNum00() {
        //doBusiness();
    }

    private void doBusiness() {
        String value = (String) redisTemplate.opsForValue().get("num");
        if (StringUtils.isEmpty(value)) {
            redisTemplate.opsForValue().set("num", "1");
        } else {
            int num = Integer.parseInt(value);
            //对num加1操作然后放入redis当中
            redisTemplate.opsForValue().set("num", String.valueOf(++num));
        }
    }

    //redis实现分布式锁一
    //分布式锁一的出现问题:如果执行业务时 锁无法释放 会一直占用
    //@Override
    public void setNum1() {
        //如果往redis里面设置值成功代表获取到了锁
        boolean acquireLock = redisTemplate.opsForValue().setIfAbsent("lock", "ok");
        if(acquireLock){
            doBusiness();
            //执行完业务逻辑之后删除锁
            redisTemplate.delete("lock");
        }else{
            //设置睡眠时间
            SleepUtils.sleepMillis(50);
            //自旋
            setNum();
        }
    }

    //redis实现分布式锁二  解决分布式锁一的问题:设置锁的过期时间
    //分布式锁二出现的问题:若业务流程执行为5秒  过期时间为3秒 则剩余2秒会进入下一个线程 从而容易导致线程2的锁被线程1干掉
    //@Override
    public void setNum2() {
        //如果往redis里面设置值成功代表获取到了锁
        boolean acquireLock = redisTemplate.opsForValue().setIfAbsent("lock", "ok",3, TimeUnit.SECONDS);
        if(acquireLock){
            doBusiness();
            //执行完业务逻辑之后删除锁
            redisTemplate.delete("lock");
        }else{
            //设置睡眠时间
            SleepUtils.sleepMillis(50);
            //自旋
            setNum();
        }
    }

    //redis实现分布式锁三  解决分布式锁二的问题:设置uuid  判断是否为自己的锁
    //分布式锁三出现的问题: 线程1执行完时,刚好判断是否为自己锁,锁过期了  线程1没执行完 线程2 继续执行1把线程2锁干掉了
    //@Override
    public void setNum3() {
        String uuid = UUID.randomUUID().toString();
        //如果往redis里面设置值成功代表获取到了锁
        boolean acquireLock = redisTemplate.opsForValue().setIfAbsent("lock", uuid);
        if(acquireLock){
            doBusiness();
            String redisUuid = (String)redisTemplate.opsForValue().get("lock");
            if (uuid.equals(redisUuid)){//是自己的锁则删除 不是 则不删除
                //执行完业务逻辑之后删除锁
                redisTemplate.delete("lock");
            }
        }else{
            //设置睡眠时间
            SleepUtils.sleepMillis(50);
            //自旋
            setNum();
        }
    }

    //redis实现分布式锁四  解决分布式锁三的问题:保证判断逻辑和删除锁逻辑(是两个语句)要具备原子性  利用lua脚本实现原子性
    //@Override
    public void setNum4() {
        String uuid = UUID.randomUUID().toString();
        //如果往redis里面设置值成功代表获取到了锁
        boolean acquireLock = redisTemplate.opsForValue().setIfAbsent("lock", uuid);
        if(acquireLock){
            doBusiness();
            //定义一个Lua脚本
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            //将script放入到redisScript中
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(script);
            //执行脚本后返回的类型是什么
            redisScript.setResultType(Long.class);
            //执行lua脚本
            redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);

            /*  相当于是下面这个:
            String redisUuid = (String)redisTemplate.opsForValue().get("lock");
            if (uuid.equals(redisUuid)){//是自己的锁则删除 不是 则不删除
                //执行完业务逻辑之后删除锁
                redisTemplate.delete("lock");
            }*/
        }else{
            //设置睡眠时间
            SleepUtils.sleepMillis(50);
            //自旋
            setNum();
        }
    }

    //通过使用Redisson实现分布式锁一
    //@Override
    public void setNum111() {
        RLock lock = redissonClient.getLock("lock");
        lock.lock();
        doBusiness();
        lock.unlock();
    }
    //Redisson实现分布式锁二
    @Override
    public void setNum() {
        RLock lock = redissonClient.getLock("lock");
        lock.lock(3,TimeUnit.SECONDS);  //3秒后锁自动释放 就不需要在调用unlock解锁了
        doBusiness();
        lock.unlock();
    }
    //Redisson实现分布式锁三
    //@Override
    public void setNum333() {
        RLock lock = redissonClient.getLock("lock");
        try {
            //第一个参数:尝试获得锁的最大等待时间  超过后没获得锁 代表取得锁失败
            //第二个参数:锁得过期时间
            //第三个参数:时间类型
            lock.tryLock(100,10,TimeUnit.SECONDS);
            doBusiness();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

}
